﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Outlook;
using OutlookContactsSync.Domain.Globals;
using OutlookContactsSync.Domain.Model;
using OutlookContactsSync.Domain.Repository;
using OutlookContactsSync.Services;
using Exception = System.Exception;
using Settings = OutlookContactsSync.Domain.Properties.Settings;

namespace OutlookContactsSync.Repository
{
    public class OutlookRepository : BaseRepository, IOutlookRepository
    {
        private readonly Application _outlookApplication = null;

        #region IOutlookRepository Members

        public OutlookRepository()
        {
            try
            {
                _outlookApplication = GetApplicationObject();
            }
            catch (COMException ex)
            {
                throw new OutlookUnavailableException("Outlook 2010 is not available.");
            }
        }

        /// <summary>
        /// Retrieve the contacts from outlook from the supplied category
        /// </summary>
        /// <param name="outlookContactCategory"></param>
        /// <param name="cancellationPending"></param>
        /// <returns></returns>
        public List<Contact> GetOutlookUsers(ref bool cancellationPending)
        {
            var outlookUsers = new List<Contact>();

            MAPIFolder folder = _outlookApplication.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);

            outlookUsers.Clear();
            int folderItemCount = folder.Items.Count;

            for (int i = 1; i < folderItemCount + 1; i++)
            {
                if (cancellationPending)
                {
                    return null;
                }

                try
                {
                    ContactItem user = folder.Items[i];

                    if (folder.Items[i].Categories == Settings.Default.ContactsCategory)
                    {
                        var item = new Contact
                                       {
                                           Id = i,
                                           MobileTelephoneNumber = user.MobileTelephoneNumber,
                                           CompanyName = user.CompanyName,
                                           WebPage = user.WebPage,
                                           OfficeLocation = user.OfficeLocation,
                                           FirstName = user.FirstName,
                                           ManagerName = user.ManagerName,
                                           Account = user.Account,
                                           BusinessTelephoneNumber = user.BusinessTelephoneNumber,
                                           Department = user.Department,
                                           FullName = user.FullName,
                                           BusinessFaxNumber = user.BusinessFaxNumber,
                                           Email1Address = user.Email1Address,
                                           JobTitle = user.JobTitle,
                                           LastName = user.LastName,
                                           NickName = user.NickName,
                                           HasPicture = user.HasPicture
                                       };

                        outlookUsers.Add(item);
                    }

                    ReportProgressBack(folderItemCount, i);
                }
                catch (Exception)
                {
                    // do niets, volgende
                }
            }

            return outlookUsers;
        }

        /// <summary>
        /// Adds the new users (which do not yet exist in Outlook) to Outlook
        /// </summary>
        /// <param name="outlookUsers"></param>
        /// <param name="domainUsers"></param>
        /// <param name="cancellationPending"></param>
        /// <returns>The amount of new users</returns>
        public int AddOutLookUsers(List<Contact> outlookUsers, List<Contact> domainUsers, ref bool cancellationPending)
        {
            List<Contact> newUsers = (from d in domainUsers
                                        where !(from o in outlookUsers
                                                select o.Account)
                                                    .Contains(d.Account)
                                        select d).ToList();

            MAPIFolder folder = _outlookApplication.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);
            
            int userCount = newUsers.Count;
            int currentUserNr = 0;

            foreach (Contact user in newUsers)
            {
                if (cancellationPending)
                {
                    return userCount;
                }

                var item = (ContactItem) folder.Items.Add(OlItemType.olContactItem);

                item.MobileTelephoneNumber = user.MobileTelephoneNumber;
                item.CompanyName = user.CompanyName;
                item.WebPage = user.WebPage;
                item.OfficeLocation = user.OfficeLocation;
                item.FirstName = user.FirstName;
                item.ManagerName = user.ManagerName;
                item.Account = user.Account;
                item.BusinessTelephoneNumber = user.BusinessTelephoneNumber;
                item.Department = user.Department;
                item.FullName = user.FullName;
                item.BusinessFaxNumber = user.BusinessFaxNumber;
                item.Email1Address = user.Email1Address;
                item.JobTitle = user.JobTitle;
                item.LastName = user.LastName;
                item.NickName = user.NickName;
                item.Categories = Settings.Default.ContactsCategory;

                string filename = AddPhoto(user.NickName, user.Picture);

                if (!string.IsNullOrEmpty(filename))
                {
                    try
                    {
                        item.AddPicture(filename);
                    }
                    catch (OutOfMemoryException ex)
                    {
                           //Outlook COM API being sloppy?
                    }
                }
                
                ReportProgressBack(userCount, currentUserNr, ImageHelper.ByteArrayToImage(user.Picture));

                item.Save();

                if (!string.IsNullOrEmpty(filename)) DeletePhoto(filename);

                currentUserNr++;
            }

            return userCount;
        }

        /// <summary>
        /// Updates the users (which exist in Outlook) from AD
        /// </summary>
        /// <param name="outlookUsers"></param>
        /// <param name="domainUsers"></param>
        /// <param name="cancellationPending"></param>
        /// <returns>Amount of users updated</returns>
        public int UpdateOutlookUsers(List<Contact> outlookUsers, List<Contact> domainUsers, ref bool cancellationPending)
        {
            if (cancellationPending)
            {
                return 0;
            }

            var updateUsers = from o in outlookUsers
                              from d in domainUsers
                              where o.Account == d.Account
                              select new {o, d};

            MAPIFolder folder = _outlookApplication.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);

            int userCount = updateUsers.Count();
            int currentUserNr = 0;

            foreach (var user in updateUsers)
            {
                if (cancellationPending)
                {
                    return userCount;
                }

                ContactItem item = folder.Items[user.o.Id];

                //Update the items that are configured to be overwritten during a sync
                if (Settings.Default.MobilePhoneChecked) item.MobileTelephoneNumber = user.d.MobileTelephoneNumber;
                if (Settings.Default.CompanyChecked) item.CompanyName = user.d.CompanyName;
                if (Settings.Default.WebPageAddress) item.WebPage = user.d.WebPage;
                if (Settings.Default.BusinessFaxChecked) item.BusinessFaxNumber = user.d.BusinessFaxNumber;
                if (Settings.Default.EmailAsChecked) item.Email1Address = user.d.Email1Address;
                if (Settings.Default.JobTitleChecked) item.JobTitle = user.d.JobTitle;
                if (Settings.Default.FullNameChecked)
                {
                    item.FullName = user.d.FullName;
                    item.FirstName = user.d.FirstName;
                    item.LastName = user.d.LastName;
                    item.NickName = user.d.NickName;
                }

                item.OfficeLocation = user.d.OfficeLocation;
                item.ManagerName = user.d.ManagerName;
                item.Account = user.d.Account;
                item.BusinessTelephoneNumber = user.d.BusinessTelephoneNumber;
                item.Department = user.d.Department;

                if (Settings.Default.PictureChecked)
                {
                    string filename = AddPhoto(user.d.NickName, user.d.Picture);

                    if (!string.IsNullOrEmpty(filename))
                    {
                        try
                        {
                            item.AddPicture(filename);
                        }
                        catch (OutOfMemoryException ex)
                        {
                            //Outlook COM API being sloppy?
                        }
                    }

                    item.Save();

                    if (!string.IsNullOrEmpty(filename)) DeletePhoto(filename);
                }
                else
                {
                    item.Save();    
                }

                currentUserNr++;
                ReportProgressBack(userCount, currentUserNr);
            }

            return userCount;
        }

        /// <summary>
        /// Removes the users (which do not exist in AD anymore) from Outlook
        /// </summary>
        /// <param name="outlookUsers"></param>
        /// <param name="domainUsers"></param>
        /// <param name="cancellationPending"></param>
        /// <returns>Removed users</returns>
        public int RemoveOutlookUsers(List<Contact> outlookUsers, List<Contact> domainUsers, ref bool cancellationPending)
        {
            if (Settings.Default.RemoveOldUsers == false) return 0;

            IOrderedEnumerable<Contact> oldUsers = from o in outlookUsers
                                                   where !(from d in domainUsers
                                                           select d.Account)
                                                              .Contains(o.Account)
                                                   orderby o.Id descending
                                                   select o;

            MAPIFolder folder = _outlookApplication.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);

            int userCount = oldUsers.Count();
            int currentUserNr = 0;

            foreach (Contact user in oldUsers)
            {
                folder.Items.Remove(user.Id);

                currentUserNr++;
                ReportProgressBack(userCount, currentUserNr);
            }

            return userCount;
        }

        #endregion

        private static string AddPhoto(string alias, byte[] data)
        {
            if (data == null) return string.Empty;

            try
            {
                string filename = Environment.GetEnvironmentVariable("TEMP") + @"\" + alias + ".jpg";

                using (var writeStream = new FileStream(filename, FileMode.Create))
                {
                    using(var writeBinary = new BinaryWriter(writeStream))
                    {
                        writeBinary.Write(data);
                        writeBinary.Close();
                    }
                }

                return filename;
            }
            catch (Exception ex)
            {
                string e = ex.Message;

                return string.Empty;
            }
        }

        private static void DeletePhoto(string filename)
        {
            if (File.Exists(filename)) File.Delete(filename);
        }

        /// <summary>
        /// Connects to an existing Outlook process if one is running or otherwise starts a new one (as a COM server).
        /// </summary>
        /// <returns>An <see cref="Application"/> instance to communicate with Outlook.</returns>
        private Application GetApplicationObject()
        {
            Application outlookApp;
            Process[] processes = Process.GetProcessesByName("OUTLOOK");
            if (processes.Length > 0)
            {
                try
                {
                    outlookApp = (Application)Marshal.GetActiveObject("Outlook.Application");
                    return outlookApp;
                }
                catch { }
            }

            outlookApp = new Application();
            return outlookApp;
        }
    }
}
